<?php
namespace app\apijson\query\operators;

class WhereRawOperator
{
    private static $config = null;
    
    /**
     * 获取配置
     */
    private static function getConfig()
    {
        if (self::$config === null) {
            self::$config = config('apijson', []);
        }
        return self::$config;
    }
    
    /**
     * 检查是否启用 @whereRaw
     */
    private static function isEnabled()
    {
        $config = self::getConfig();
        return $config['enable_where_raw'] ?? true;
    }
    
    public static function apply($query, $value)
    {
        // 检查是否启用 @whereRaw
        if (!self::isEnabled()) {
            throw new \app\apijson\core\ApiJsonException('@whereRaw 操作符已被禁用');
        }
        
        // 支持多种格式：
        // 1. 单个表达式: "FIND_IN_SET('value', field) > 0"
        // 2. 数组格式: ["FIND_IN_SET('value', field) > 0", "field2 = ?"]
        // 3. 对象格式: {"expression": "FIND_IN_SET('value', field) > 0", "bindings": ["value"]}
        // 4. 复合对象格式: {"expr1": "FIND_IN_SET('value', field) > 0", "expr2": ["field = ?", ["value"]]}
        
        if (is_string($value)) {
            // 单个表达式
            self::validateExpression($value);
            $query->whereRaw($value);
        } elseif (is_array($value)) {
            if (self::isIndexedArray($value)) {
                // 数组格式：多个表达式
                foreach ($value as $expression) {
                    self::processExpression($query, $expression);
                }
            } else {
                // 对象格式
                if (isset($value['expression'])) {
                    // 单个表达式带绑定参数
                    self::validateExpression($value['expression']);
                    $bindings = $value['bindings'] ?? [];
                    self::validateBindings($bindings);
                    $query->whereRaw($value['expression'], $bindings);
                } else {
                    // 多个表达式
                    foreach ($value as $key => $val) {
                        self::processExpression($query, $val);
                    }
                }
            }
        } else {
            throw new \app\apijson\core\ApiJsonException('@whereRaw 值必须是字符串或数组格式');
        }
    }
    
    /**
     * 处理单个表达式
     */
    private static function processExpression($query, $expression)
    {
        if (is_string($expression)) {
            // 字符串表达式
            self::validateExpression($expression);
            $query->whereRaw($expression);
        } elseif (is_array($expression)) {
            if (count($expression) >= 2) {
                // 数组格式：["expression", "bindings"]
                self::validateExpression($expression[0]);
                $bindings = $expression[1] ?? [];
                self::validateBindings($bindings);
                $query->whereRaw($expression[0], $bindings);
            } else {
                // 单个元素的数组，当作字符串处理
                self::validateExpression($expression[0]);
                $query->whereRaw($expression[0]);
            }
        } else {
            throw new \app\apijson\core\ApiJsonException('@whereRaw 表达式必须是字符串或数组格式');
        }
    }
    
    /**
     * 验证SQL表达式安全性
     */
    private static function validateExpression($expression)
    {
        if (!is_string($expression)) {
            throw new \app\apijson\core\ApiJsonException('SQL表达式必须是字符串');
        }
        
        $config = self::getConfig();
        
        // 检查表达式长度
        $maxLength = $config['max_expression_length'] ?? 1000;
        if (strlen($expression) > $maxLength) {
            throw new \app\apijson\core\ApiJsonException("SQL表达式过长，最大允许{$maxLength}字符");
        }
        
        // 转换为大写进行检查
        $upperExpression = strtoupper($expression);
        
        // 检查危险关键词（使用更精确的匹配）
        $dangerousKeywords = $config['dangerous_keywords'] ?? [];
        foreach ($dangerousKeywords as $keyword) {
            if (self::isDangerousKeyword($upperExpression, $keyword)) {
                throw new \app\apijson\core\ApiJsonException("SQL表达式包含危险关键词: {$keyword}");
            }
        }
        
        // 检查是否包含允许的函数（可选，如果启用严格模式）
        if (self::isStrictMode()) {
            $allowedFunctions = $config['allowed_functions'] ?? [];
            $hasAllowedFunction = false;
            foreach ($allowedFunctions as $function) {
                if (strpos($upperExpression, $function) !== false) {
                    $hasAllowedFunction = true;
                    break;
                }
            }
            
            if (!$hasAllowedFunction) {
                throw new \app\apijson\core\ApiJsonException('SQL表达式必须包含允许的函数');
            }
        }
        
        // 检查字段名格式
        self::validateFieldNames($expression);
    }
    
    /**
     * 检查是否为危险关键词（更精确的匹配）
     */
    private static function isDangerousKeyword($expression, $keyword)
    {
        // 对于某些关键词，需要更精确的匹配
        $contextualKeywords = ['SET', 'SELECT', 'FROM', 'WHERE', 'AND', 'OR'];
        
        if (in_array($keyword, $contextualKeywords)) {
            // 检查是否作为独立关键词出现（前后有空格或特殊字符）
            $pattern = '/\b' . preg_quote($keyword, '/') . '\b/';
            if (preg_match($pattern, $expression)) {
                // 进一步检查上下文
                return self::isDangerousContext($expression, $keyword);
            }
            return false;
        } else {
            // 对于其他关键词，使用简单的字符串匹配
            return strpos($expression, $keyword) !== false;
        }
    }
    
    /**
     * 检查关键词的上下文是否危险
     */
    private static function isDangerousContext($expression, $keyword)
    {
        switch ($keyword) {
            case 'SET':
                // 检查是否在危险上下文中使用 SET
                $dangerousPatterns = [
                    '/\bSET\s+[A-Z_]+=/',  // SET variable = value
                    '/\bSET\s+@/',          // SET @variable
                    '/\bSET\s+SESSION/',    // SET SESSION
                    '/\bSET\s+GLOBAL/',     // SET GLOBAL
                ];
                
                foreach ($dangerousPatterns as $pattern) {
                    if (preg_match($pattern, $expression)) {
                        return true;
                    }
                }
                // FIND_IN_SET 中的 SET 是安全的
                if (strpos($expression, 'FIND_IN_SET') !== false) {
                    return false;
                }
                return false;
                
            case 'SELECT':
                // 检查是否在危险上下文中使用 SELECT
                $dangerousPatterns = [
                    '/\bSELECT\s+\*/',      // SELECT *
                    '/\bSELECT\s+.*\s+FROM\s+information_schema/',
                    '/\bSELECT\s+.*\s+FROM\s+mysql\./',
                ];
                
                foreach ($dangerousPatterns as $pattern) {
                    if (preg_match($pattern, $expression)) {
                        return true;
                    }
                }
                return false;
                
            default:
                return false;
        }
    }
    
    /**
     * 验证绑定参数
     */
    private static function validateBindings($bindings)
    {
        if (!is_array($bindings)) {
            throw new \app\apijson\core\ApiJsonException('绑定参数必须是数组格式');
        }
        
        $config = self::getConfig();
        $maxBindingLength = $config['max_binding_length'] ?? 255;
        $dangerousKeywords = $config['dangerous_keywords'] ?? [];
        
        foreach ($bindings as $binding) {
            if (is_string($binding) && strlen($binding) > $maxBindingLength) {
                throw new \app\apijson\core\ApiJsonException("绑定参数过长，最大允许{$maxBindingLength}字符");
            }
            
            // 检查绑定参数是否包含SQL注入特征
            if (is_string($binding)) {
                $upperBinding = strtoupper($binding);
                foreach ($dangerousKeywords as $keyword) {
                    if (strpos($upperBinding, $keyword) !== false) {
                        throw new \app\apijson\core\ApiJsonException("绑定参数包含危险关键词: {$keyword}");
                    }
                }
            }
        }
    }
    
    /**
     * 验证字段名格式
     */
    private static function validateFieldNames($expression)
    {
        $config = self::getConfig();
        $allowedFunctions = $config['allowed_functions'] ?? [];
        
        // 提取字段名（简单的正则匹配）
        preg_match_all('/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/', $expression, $matches);
        
        foreach ($matches[1] as $field) {
            // 跳过已知的SQL函数和关键字
            if (in_array(strtoupper($field), $allowedFunctions) || 
                in_array(strtoupper($field), ['AND', 'OR', 'NOT', 'IN', 'LIKE', 'BETWEEN', 'IS', 'NULL'])) {
                continue;
            }
            
            // 验证字段名格式
            if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field)) {
                throw new \app\apijson\core\ApiJsonException("非法字段名: {$field}");
            }
        }
    }
    
    /**
     * 判断是否为严格模式
     */
    private static function isStrictMode()
    {
        $config = self::getConfig();
        return $config['strict_where_raw'] ?? false;
    }
    
    /**
     * 判断是否为索引数组
     */
    private static function isIndexedArray($array)
    {
        if (empty($array)) {
            return true;
        }
        return array_keys($array) === range(0, count($array) - 1);
    }
}
